﻿///<reference path="_references.ts" />

module ScrambledEggs {

    export interface IDrawable {
	    Draw(time: number): void;
    }

    export interface ILoopState {
        Start: number;
        Time: () => number;
        GetMouseState: () => IMouseState;
        GetKeyState: () => KeyState;
        GetActualFps: () => number;
    }

    export interface IMouseState {
        X: number;
        Y: number;
        Down: bool;
        PreviouslyDown: bool;
        PositionChanged: bool;
    }

    export class KeyState {
        public _keysDown: { [keyCode: number]: bool; } = <{ [keyCode: number]: bool; }>{};

        KeyDown(keyCode: number) {
            this._keysDown[keyCode] = true;
        }
        KeyUp(keyCode: number) {
            this._keysDown[keyCode] = false;
        }

        public KeyArrowLeft(): bool {
            return this._keysDown[37] == true;
        }
        public KeyArrowUp(): bool {
            return this._keysDown[38] == true;
        }
        public KeyArrowRight(): bool {
            return this._keysDown[39] == true;
        }
        public KeyArrowDown(): bool {
            return this._keysDown[40] == true;
        }
    }

    export class DrawLoop {
        private static _loopCounter = 0;
        private static _alwaysPrint = false; // debug constant
        private static _activeLoopCounter = 0;
        private static _undisposedLoopCounter = 0;
        
        private _frameCount: number = 0;
        private _updateCount: number = 0;
        private _targetFps: number = 60;
        private _actualFps: number = 60;
        private _actualUps: number = 60;

        private _enabled: bool = false;
        private _drawloop: number;
        private _updateloop: number;
        private _drawables: IDrawable[];

        private _loopNumber: number;
        private _drawFunc: () => void;
        private _updateFunc: (state: ILoopState) => void;
        private _state: ILoopState;

        constructor(updateFunction: (state: ILoopState) => void, targetFps: number = 60, ...drawables: IDrawable[]) {
            var me = this;
            me._loopNumber = DrawLoop._loopCounter++;
            console.log("Creating DrawLoop " + me._loopNumber);

            me._updateFunc = updateFunction;
            var start = (new Date()).getTime();
            var mouse = { X: 0, Y: 0, Down: false, PreviouslyDown: false, PositionChanged: false };
            var keys = new KeyState();
            me._state = {
                Start: start,
                Time: function () { return (new Date()).getTime() - start; },
                GetKeyState: function () { return keys; },
                GetMouseState: function () { return mouse; },
                GetActualFps: function () { return me._actualFps; },
            };

            me._drawFunc = (function () {
                return typeof requestAnimationFrame === 'undefined'
                    ? function () { me.performDraw(); }
                    : function () { requestAnimationFrame(function () { me.performDraw(); }); };
            })();

            setInterval(function () { me.performLoop(); }, 1000);
            
            me.setFps(targetFps);
            me.setDrawables(drawables);
            me.enable();
            DrawLoop._undisposedLoopCounter++;
        }

        public disable(): void {
            var me = this;
            console.log("Disabling DrawLoop " + me._loopNumber);

            if (me._enabled)
                DrawLoop._activeLoopCounter--;
            me._enabled = false;

            if (me._drawloop != null) {
                clearInterval(me._drawloop);
                me._drawloop = null;
            }
            if (me._updateloop) {
                clearInterval(me._updateloop);
                me._updateloop = null;
            }

            var ns = "drawloop" + me._loopNumber;
            $(document).off("mousemove." + ns + " touchmove." + ns);
            $(document).off("mousedown." + ns + " touchstart." + ns);
            $(document).off("mouseup." + ns + " touchend." + ns);
            $(document).off("keydown." + ns);
            $(document).off("keyup." + ns);
        }
        public dispose(): void {
            var me = this;
            console.log("Disposing DrawLoop " + me._loopNumber);
            me.disable();
            delete me._drawables;
            delete me._loopNumber;
            delete me._drawFunc;
            delete me._updateFunc;
            delete me._state;
            DrawLoop._undisposedLoopCounter--;
        }
        public enable(): void {
            var me = this;
            if (me._drawloop != null) {
                console.warn("cannot enable without disabling first");
                return;
            }

            console.log("Enabling DrawLoop " + me._loopNumber);

            me._enabled = true;
            me._drawloop = setInterval(me._drawFunc, 1000 / me._targetFps);
            me._updateloop = setInterval(function () { me._updateFunc(me._state); }, 1000 / me._targetFps / 2); // update max twice per frame

            var mouse = me._state.GetMouseState();
            var keys = me._state.GetKeyState();
            var ns = "drawloop" + me._loopNumber;
            $(document).on("mousemove." + ns + " touchmove." + ns, function (e) {
                mouse.X = e.pageX;
                mouse.Y = e.pageY;
                mouse.PositionChanged = true;
            });
            $(document).on("mousedown." + ns + " touchstart." + ns, function () {
                mouse.Down = true;
            });
            $(document).on("mouseup." + ns + " touchend." + ns, function () {
                mouse.Down = false;
            });
            $(document).on("keydown." + ns, function (e) {
                keys.KeyDown(e.which);
                e.preventDefault();
                return false;
            });
            $(document).on("keyup." + ns, function (e) {
                keys.KeyUp(e.which);
                e.preventDefault();
                return false;
            });
        }

        private performDraw() {
            var me = this;
            me._frameCount++;
            var time = me._state.Time();

            $.each(me._drawables, function () {
                var drawable: IDrawable = this;
                if (drawable.Draw != null)
                    drawable.Draw(time);
            });
        }

        private performUpdate() {
            var me = this;
            me._updateCount++;
            me._updateFunc(me._state);
            var mouseState = me._state.GetMouseState();
            mouseState.PreviouslyDown = mouseState.Down;
            mouseState.PositionChanged = false;
        }

        private getDefault(value, def): any {
            return value == null ? value : def;
        }

        public setDrawables(drawables: IDrawable[]): void {
            var me = this;
            me._drawables = [];
            if (drawables != null && drawables.length > 0) {
                $.each(drawables, function () {
                    me._drawables.push(this);
                });
                if (me._enabled) {
                    me.disable();
                    me.enable();
                }
            }
        }
        public setFps(targetFps: number): void {
            var me = <DrawLoop>this;
            me._targetFps = me.getDefault(targetFps, 60);
            if (me._enabled) {
                me.disable();
                me.enable();
            }
        };

        private performLoop(): void {
            var me = this;
            if (!me._enabled)
                return;
            me._actualFps = me._frameCount;
            me._actualUps = me._updateCount;
            me._frameCount = 0;
            me._updateCount = 0;
            if ((me._actualFps < me._targetFps * 0.9 || me._actualFps > me._targetFps * 1.1 || DrawLoop._alwaysPrint) && me._actualFps > 1)
                console.warn("DrawLoop " + me._loopNumber + " | fps: " + me._actualFps + "/" + me._targetFps + " | ups: " + me._actualUps);
        }
    }
}